#include <jni.h>
#include <string>
#include <unistd.h>
#include "com_company_ndklearnproject_utils_VoiceChangeUtils.h"

#include "android/log.h"

using namespace std;
using namespace FMOD;

#define TAG "NDK_JNI_LEARN_TAG"
// 自定义宏函数来进行java层日志打印 固定写法
#define JNI_LOG_D(...) __android_log_print(ANDROID_LOG_DEBUG,TAG,__VA_ARGS__)
#define JNI_LOG_I(...) __android_log_print(ANDROID_LOG_INFO,TAG,__VA_ARGS__)
#define JNI_LOG_E(...) __android_log_print(ANDROID_LOG_ERROR,TAG,__VA_ARGS__)

// 声明一个函数，可以去其他类中实现，
extern void show();
/**
 * extern "C"：表示严格使用C风格编译模式，因为最终调用的都是C代码  可以查看 JNIEnv* env
 * JNIEnv 这个是C中的结构体，JNI打通JAVA与Native主要考它 使用到的所有函数都在JNINativeInterface中
 * JNIEXPORT：表示供外部调用的标识
 * jstring：返回类型 JNI识别的类型，用于Java String 和 Native char * 的相互转换
 * 还有很多其他的类型，比如jobject jclass等等
 * JNICALL：JIN函数表示，可以不写，方便代码阅读，一般都带上
 *
 * JNIEnv* env ： 这个参数很重要，Java 与 C 之间的桥梁，JNI的学习，大部分都是JNIEnv这个中的函数使用
 * jobject ： 这个参数表示调用者的实例，这里相当于MainActivity的实例  对应Java中的MainActivity.this
 *
 */
extern "C" JNIEXPORT jstring JNICALL
Java_com_company_ndklearnproject_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

extern "C" JNIEXPORT void JNICALL
Java_com_company_ndklearnproject_MainActivity_changeStringFromJNI(
        JNIEnv *env, jobject thiz) {
    // 获取当前类的实例
    jclass j_clazz = env->GetObjectClass(thiz);
    /**
     * 获取当前类实例的某个属性
     * 第一个参数：类的实例
     * 第二个参数：类中对应的属性名
     * 第三个参数：属性签名 表示属性名的类型  object = "L + 该属性对应的类型包名 + 类名 + ;"  固定写法
     */
    jfieldID j_field_id = env->GetFieldID(
            j_clazz, "name", "Ljava/lang/String;");
    jstring j_str = static_cast<jstring>(env->GetObjectField(thiz, j_field_id));
    char *c_str = const_cast<char *>(env->GetStringUTFChars(j_str, nullptr));
    JNI_LOG_I("JNI中获取MainActivity中name的值 == %s", c_str);
    //
    jstring new_j_str = env->NewStringUTF("Jany");
//    env->SetObjectField(thiz,j_fieldID,name);
    // 修改调用者的某个属性的值
    env->SetObjectField(thiz, j_field_id, new_j_str);
    char *c_new_str = const_cast<char *>(env->GetStringUTFChars(new_j_str, nullptr));
    JNI_LOG_I("更改MainActivity中name的值 == %s", c_new_str);
}

extern "C" JNIEXPORT void JNICALL
Java_com_company_ndklearnproject_MainActivity_changeAgeFromJNI(
        JNIEnv *env, jobject thiz) {
    jclass j_class = env->GetObjectClass(thiz);
    // int类型可以写成"I"就ok了  静态类型调用的api也变化了
    jfieldID j_fid = env->GetStaticFieldID(j_class, "sAge", "I");
    jint j_age = env->GetStaticIntField(j_class, j_fid);
    // int 类型 可以直接转换  不需要通过JNI调用函数来转
    int c_age = j_age;
    JNI_LOG_I("JNI中获取MainActivity中age的值 == %d", c_age);
    j_age = j_age + 1;
    env->SetStaticIntField(j_class, j_fid, j_age);
    JNI_LOG_I("JNI中修改MainActivity中age后的值 == %d", j_age);
}

/**
 * 签名规则
 * Java的 boolean -->  Z  ***与首字母不一样
 * Java的 byte -->  B
 * Java的 char -->  C
 * Java的 short -->  S
 * Java的 int -->  I
 * Java的 long -->  J    ***与首字母不一样
 * Java的 float -->  F
 * Java的 double -->  D
 * Java的 object -->  L+包名+类名+;
 * Java的 String -->  Ljava/lang/String;  按上面的规则来
 * Java的 array int[] -->  [I
 * Java的 array double[][] -->  [[D   double类型的二维数组
 * 更复杂的类就不用记了，可以通过命令去查
 * javap -s -p xxx.class
 * -s 输出xxx.class的所有属性和方法的签名
 * -p 忽略公开和私有全部进行输出
 *
 */
extern "C" JNIEXPORT void JNICALL
Java_com_company_ndklearnproject_MainActivity_callMethodFromJNI(
        JNIEnv *env, jobject thiz) {
    jclass j_class = env->GetObjectClass(thiz);
    // 函数签名： ()V 表示无参无返回
    jmethodID j_print_mid = env->GetMethodID(j_class, "printInfo", "()V");
    // 函数签名： ()I 表示无参返回int
    jmethodID j_get_age_mid = env->GetMethodID(j_class, "getAge", "()I");
    jint age = env->CallIntMethod(thiz, j_get_age_mid);
    JNI_LOG_I("JNI通过函数获取age的值 == %d", age);
    // 调用Java方法
    env->CallVoidMethod(thiz, j_print_mid);
}

extern "C" JNIEXPORT void JNICALL
Java_com_company_ndklearnproject_MainActivity_callParamsFromJNI(
        JNIEnv *env, jobject thiz,
        jint count, jstring info,
        jintArray numbers,
        jobjectArray objects) {

    // C中接受Java的参数
    int c_count = count;
    JNI_LOG_I("callParamsFromJNI JIN接受的第1个参数 count = %d", count);
    char *c_info = const_cast<char *>(env->GetStringUTFChars(info, nullptr));
    JNI_LOG_I("callParamsFromJNI JIN接受的第2个参数 count = %s", c_info);
    jint *j_array = env->GetIntArrayElements(numbers, nullptr);
    jsize j_array_size = env->GetArrayLength(numbers);
    JNI_LOG_I("callParamsFromJNI JIN接受的第3个参数 numbers = ");
    for (int i = 0; i < j_array_size; ++i) {
        JNI_LOG_I("callParamsFromJNI numbers[%d] = %d", i, *(j_array + i));
        // 修改传入的参数，在原有基础上 +1
        *(j_array + i) = *(j_array + i) + 1;
    }
    // C中接收到的数组被改变后，可以同步改变Java层的传入的数组
    env->ReleaseIntArrayElements(numbers, j_array, 0);
    JNI_LOG_I("callParamsFromJNI JIN接受的第4个参数 objects = ");
    jsize j_object_array_size = env->GetArrayLength(objects);
    for (int i = 0; i < j_object_array_size; ++i) {
        auto j_object_string = static_cast<jstring>(env->GetObjectArrayElement(objects, i));
        char *c_object_string = const_cast<char *>(env->GetStringUTFChars(j_object_string,
                                                                          nullptr));
        JNI_LOG_I("callParamsFromJNI objects[%d] = %s", i, c_object_string);
        // 用完后释放内存 jstring
        env->ReleaseStringUTFChars(j_object_string, c_object_string);
    }
}
extern "C" JNIEXPORT void JNICALL
Java_com_company_ndklearnproject_MainActivity_callObjectFromJNI(
        JNIEnv *env, jobject thiz,
        jobject person) {
    // JavaBean 转 jclass 的两种方法 一般直接用第二种了
    jclass j_person_0 = env->FindClass("com/company/ndklearnproject/bean/Person");
    jclass j_person = env->GetObjectClass(person);
    // Person的setName方法
    jmethodID j_set_name = env->GetMethodID(j_person, "setName", "(Ljava/lang/String;)V");
    jmethodID j_get_age = env->GetMethodID(j_person, "getAge", "()I");
    jmethodID j_show_info = env->GetStaticMethodID(j_person, "showInfo", "(Ljava/lang/String;)V");

    jstring j_name = env->NewStringUTF("Toosan");
    char *c_name = const_cast<char *>(env->GetStringUTFChars(j_name, nullptr));
    JNI_LOG_I("JIN通过Person setName() 给Person Name赋值 name = %s", c_name);
    env->CallVoidMethod(person, j_set_name, j_name);
    jint j_age = env->CallIntMethod(person, j_get_age);
    JNI_LOG_I("JIN通过Person getAge() 获取的age = %d", j_age);

    jstring j_str_show_info = env->NewStringUTF("JNI调用了showInfo()");
    env->CallStaticVoidMethod(j_person, j_show_info, j_str_show_info);
    char *c_show_info = const_cast<char *>(env->GetStringUTFChars(j_str_show_info, nullptr));
    JNI_LOG_I("JIN通过Person 调用静态方法showInfo() info = %s", c_show_info);
}

extern "C" JNIEXPORT jobject JNICALL
Java_com_company_ndklearnproject_MainActivity_callGetObjectFromJNI(
        JNIEnv *env, jobject thiz) {
    const char *c_person_path = "com/company/ndklearnproject/bean/Person";
    jclass j_person = env->FindClass(c_person_path);
//    jclass j_person_clz = env->GetObjectClass(j_person);
    jmethodID j_set_name = env->GetMethodID(j_person, "setName", "(Ljava/lang/String;)V");
    jmethodID j_set_age = env->GetMethodID(j_person, "setAge", "(I)V");
    jobject j_person_obj = env->AllocObject(j_person); // 生成对象 不会走构造方法
//    jobject j_person_obj = env->NewObject(j_person); // 生成对象，会走构造方法
    // 往对象中赋值
    jstring j_name = env->NewStringUTF("Toosan");
    env->CallVoidMethod(j_person_obj, j_set_name, j_name);
    env->CallVoidMethod(j_person_obj, j_set_age, 25);
    // Person构建完毕
    // 再构建一个Student
    const char *c_student_path = "com/company/ndklearnproject/bean/Student";
    jclass j_student_clz = env->FindClass(c_student_path);
    jmethodID j_set_school = env->GetMethodID(j_student_clz, "setSchoolName",
                                              "(Ljava/lang/String;)V");
    jmethodID j_set_number = env->GetMethodID(j_student_clz, "setStudentNo",
                                              "(Ljava/lang/String;)V");
    jobject j_student_object = env->AllocObject(j_student_clz);
    jstring j_school = env->NewStringUTF("吉首大学");
    env->CallVoidMethod(j_student_object, j_set_school, j_school);
    jstring j_number = env->NewStringUTF("20154206073");
    env->CallVoidMethod(j_student_object, j_set_number, j_number);
    // Student构建完毕
    // 把Student放到Person中
    jmethodID j_set_student = env->GetMethodID(j_person, "setStudent",
                                               "(Lcom/company/ndklearnproject/bean/Student;)V");
    env->CallVoidMethod(j_person_obj, j_set_student, j_student_object);
//    env->DeleteLocalRef(j_person);
//    env->DeleteLocalRef(j_person_obj);
//    env->DeleteLocalRef(j_student_clz);
    // 把构建好的Person返回给Java层
    return j_person_obj;

}

/**
 * 就算定义在函数外面，它依然是局部变量   JIN的知识
 * 当函数执行完后，会进行释放
 * 虽然内存被清空了，但是并不为NULL，是一个悬空指针
 * 当第二次执行的时候就会异常
 *
 * 解决：改为全局变量，由自己手动去释放，一般把释放方法写好，Java层在onDestroy中去调用
 */
jclass j_dog_clz;

extern "C" JNIEXPORT jobject JNICALL
Java_com_company_ndklearnproject_MainActivity_callGetDogFromJNI(
        JNIEnv *env, jobject thiz) {
    if (j_dog_clz == nullptr) {
        const char *c_dog_path = "com/company/ndklearnproject/bean/Dog";
        jclass temp = env->FindClass(c_dog_path);
        // 转换为全局引用
        j_dog_clz = static_cast<jclass>(env->NewGlobalRef(temp));
        // 用完后及时清理
        env->DeleteLocalRef(temp);
    }

    // 所有构造函数不需要返回值，通过形参判定调用哪个构造
    jmethodID j_dog_construct0 = env->GetMethodID(j_dog_clz, "<init>", "()V");
    jmethodID j_dog_construct1 = env->GetMethodID(j_dog_clz, "<init>", "(Ljava/lang/String;)V");
    jmethodID j_dog_construct2 = env->GetMethodID(j_dog_clz, "<init>", "(Ljava/lang/String;I)V");
    jmethodID j_dog_construct3 = env->GetMethodID(j_dog_clz, "<init>",
                                                  "(Ljava/lang/String;ILjava/lang/String;)V");
    jstring j_name = env->NewStringUTF("Hac");
    jint j_age = 3;
    jstring j_color = env->NewStringUTF("black");
    jobject j_dog_object0 = env->NewObject(j_dog_clz, j_dog_construct0);
    jobject j_dog_object1 = env->NewObject(j_dog_clz, j_dog_construct1, j_name);
    jobject j_dog_object2 = env->NewObject(j_dog_clz, j_dog_construct2, j_name, j_age);
    jobject j_dog_object3 = env->NewObject(j_dog_clz, j_dog_construct3, j_name, j_age, j_color);
    show();
    return j_dog_object3;

}

extern "C" JNIEXPORT void JNICALL
Java_com_company_ndklearnproject_MainActivity_callDeleteDogFromJNI(
        JNIEnv *env, jobject thiz) {
    env->DeleteGlobalRef(j_dog_clz);
//    j_dog_clz == nullptr;
    JNI_LOG_I("JIN 全局引用j_dog_clz已经释放完成");
}


char *c_file_path;
// 声音引擎
System *system_voice = 0;
// 声音
Sound *sound = 0;
// 音轨（通道）
Channel *channel = 0;
// 数字信号
DSP *dsp = 0;

extern "C" JNIEXPORT void JNICALL
Java_com_company_ndklearnproject_utils_VoiceChangeUtils_changeLuoliVoice(
        JNIEnv *env, jclass thiz, jint mode, jstring file_path) {
    JNI_LOG_I("JIN voice entry...");
    char *tip_content = "声音播放完成";
    c_file_path = const_cast<char *>(env->GetStringUTFChars(file_path, nullptr));
    // 创建引擎
    System_Create(&system_voice);
    // 初始化引擎  最大通道数，引擎初始化标记，额外数据
    system_voice->init(32, FMOD_INIT_NORMAL, 0);
    // 初始化声音  声音文件路径，声音初始化标记，额外数据，声音指针
    system_voice->createSound(c_file_path, FMOD_DEFAULT, 0, &sound);
    // 播放前，把之前的都停止掉
    channel->stop();
    // 播放声音   声音，音轨是否分组，是否停止其他声音，音轨
    system_voice->playSound(sound, 0, false, &channel);
    JNI_LOG_I("JIN voice mode = %d", mode);
    switch (mode) {
        case com_company_ndklearnproject_utils_VoiceChangeUtils_MODE_LUOLI:
            // 创建数字信号
            system_voice->createDSPByType(FMOD_DSP_TYPE_PITCHSHIFT, &dsp);
            // 音调调节
            dsp->setParameterFloat(FMOD_DSP_PITCHSHIFT_PITCH, 2.0f);
            // 在音轨上添加调节后的音调
            channel->addDSP(0, dsp);
            JNI_LOG_I("JIN voice channel addDSP LUOLI");
            break;
        case com_company_ndklearnproject_utils_VoiceChangeUtils_MODE_GAOGUAI:
            // 获取音轨声音播放频率
            float frequency;
            channel->getFrequency(&frequency);
            // 1.5倍速
            channel->setFrequency(frequency * 1.25f);
            JNI_LOG_I("JIN voice channel addDSP GAOGUAI ");
            break;
        case com_company_ndklearnproject_utils_VoiceChangeUtils_MODE_NORMAL:
        default:
            JNI_LOG_I("JIN voice channel default ");
            break;
    }
    JNI_LOG_I("JIN voice playSound... c_file_path = %s", c_file_path);
    bool isPlay = true;
    while (isPlay) {
        channel->isPlaying(&isPlay);
        usleep(1000 * 1000);
    }
    JNI_LOG_I("JIN voice playSound finish... c_file_path = %s", c_file_path);
    // 释放资源
    sound->release();
    system_voice->close();
    system_voice->release();
}

extern "C" JNIEXPORT void JNICALL
Java_com_company_ndklearnproject_utils_VoiceChangeUtils_stopPlayVoice(JNIEnv *env, jclass clazz) {
    channel->stop();
}